"""
MQTT客户端程序 - 支持多主题发布版本
功能：订阅多个主题 + 向多个主题定时发布JSON消息 + 可配置发布间隔
作者：AI助手
版本：1.2
更新内容：
1. 支持向多个主题发布消息
2. 优化发布线程逻辑
3. 增强错误处理
"""

import paho.mqtt.client as mqtt
import threading
import time
import json

class MQTTClient:
    def __init__(self):
        """
        初始化MQTT客户端
        - 使用V2版本的MQTT回调API
        - 设置订阅/发布主题列表
        - 初始化状态标志
        """
        # 使用V2版API避免弃用警告
        self.client = mqtt.Client(callback_api_version=mqtt.CallbackAPIVersion.VERSION2)
        
        # 绑定回调函数
        self.client.on_connect = self.on_connect
        self.client.on_message = self.on_message
        
        # 线程控制相关
        self.publish_timer = None
        self.running = False
        
        # 订阅的主题列表（可根据需要修改）
        self.subscribe_topics = [
            "ppjia0",  # 订阅主题1
            "ppjia1",  # 订阅主题2
            "ppjia2"   # 订阅主题3
        ]
        
        # 发布的主题列表（可根据需要修改）
        self.publish_topics = [
            "ddjia0",  # 发布主题1
            "ddjia1",  # 发布主题2
            "ddjia2"   # 发布主题3
        ]
        
        # 固定格式的JSON消息模板（可根据需要修改）
        self.message_template = {
            "type": 1,                    # 消息类型标识
            "value": "010200010002cff2",  # 示例数据值
            "arg": 12144                  # 示例参数
        }

    def on_connect(self, client, userdata, flags, rc, properties=None):
        """
        连接回调函数
        - rc=0表示成功连接
        - 连接成功后自动订阅预设主题
        - 启动发布线程
        """
        print(f"连接结果代码: {rc} (0表示成功)")
        if rc == 0:
            # 订阅所有预设主题
            for topic in self.subscribe_topics:
                client.subscribe(topic)
                print(f"已订阅主题: {topic}")
            
            # 启动定时发布
            self.start_publishing()
        else:
            print("连接失败！请检查网络和broker设置")

    def on_message(self, client, userdata, msg):
        """
        消息接收回调函数
        - 打印接收到的消息内容和主题
        - 包含基本的错误处理
        """
        try:
            payload = msg.payload.decode('utf-8')
            print(f"收到消息 [{msg.topic}]: {payload}")
        except UnicodeDecodeError:
            print(f"消息解码失败 [主题: {msg.topic}]")
        except Exception as e:
            print(f"处理消息时出错: {str(e)}")

    def publish_message(self):
        """
        消息发布线程函数
        - 循环向所有发布主题发布预设的JSON消息
        - 保持指定的发布间隔
        - 包含错误处理和恢复机制
        """
        while self.running:
            try:
                # 序列化JSON消息
                message = json.dumps(self.message_template)
                
                # 向所有发布主题发送消息
                for topic in self.publish_topics:
                    self.client.publish(
                        topic=topic,
                        payload=message,
                        qos=0,
                        retain=False
                    )
                    print(f"已发布到 [{topic}]: {message}")
                
                time.sleep(self.publish_interval)
                
            except json.JSONDecodeError:
                print("JSON序列化失败！检查消息模板")
                time.sleep(1)
            except Exception as e:
                print(f"发布错误: {str(e)}")
                time.sleep(1)  # 出错时等待1秒

    def start_publishing(self):
        """
        启动发布线程
        - 设置运行标志
        - 创建守护线程
        - 打印启动信息
        """
        self.running = True
        self.publish_timer = threading.Thread(
            target=self.publish_message,
            daemon=True  # 设置为守护线程
        )
        self.publish_timer.start()
        print(f"已启动定时发布，间隔: {int(self.publish_interval*1000)}ms")
        print(f"发布主题列表: {', '.join(self.publish_topics)}")

    def stop_publishing(self):
        """
        停止发布线程
        - 清除运行标志
        - 等待线程结束
        """
        self.running = False
        if self.publish_timer and self.publish_timer.is_alive():
            self.publish_timer.join(timeout=2)  # 最多等待2秒
        print("已停止发布")

    def connect(self, broker_address, broker_port, publish_interval_ms):
        """
        连接broker主函数
        - 参数说明:
          broker_address: broker服务器地址
          broker_port: broker服务端口
          publish_interval_ms: 发布间隔(毫秒)
        """
        # 转换毫秒为秒(内部使用秒为单位)
        self.publish_interval = publish_interval_ms / 1000.0
        
        try:
            print(f"正在连接服务器: {broker_address}:{broker_port}...")
            
            # 设置连接超时60秒
            self.client.connect(
                host=broker_address,
                port=broker_port,
                keepalive=60
            )
            
            # 启动网络循环(阻塞调用)
            self.client.loop_forever()
            
        except ConnectionRefusedError:
            print("连接被拒绝！请检查:")
            print("1. broker地址和端口是否正确")
            print("2. broker服务是否运行")
            print("3. 防火墙设置")
        except Exception as e:
            print(f"连接时发生错误: {str(e)}")
        except KeyboardInterrupt:
            print("\n正在断开连接...")
            self.stop_publishing()
            self.client.disconnect()
            print("已断开连接")

if __name__ == "__main__":
    # 程序入口
    print("="*50)
    print("MQTT客户端程序 - 多主题发布版")
    print("功能: 多主题订阅 + 多主题定时JSON发布")
    print("="*50)
    print("""
    MQTT客户端程序使用说明
    -----------------------------
    1. 按提示输入服务器地址和端口
    2. 输入发布间隔（毫秒）
    3. 按Ctrl+C退出程序
    """)
    
    # 默认配置
    DEFAULT_BROKER = "broker.emqx.io"
    DEFAULT_PORT = 1883
    DEFAULT_INTERVAL_MS = 100  # 默认100毫秒
    
    # 获取用户输入
    broker_address = input(f"输入broker地址(回车使用默认 {DEFAULT_BROKER}): ").strip()
    broker_port = input(f"输入端口号(回车使用默认 {DEFAULT_PORT}): ").strip()
    interval_input = input(f"输入发布间隔(毫秒, 回车使用默认 {DEFAULT_INTERVAL_MS}): ").strip()
    
    # 处理输入
    broker_address = broker_address or DEFAULT_BROKER
    try:
        broker_port = int(broker_port) if broker_port else DEFAULT_PORT
    except ValueError:
        print("无效的端口号，使用默认值")
        broker_port = DEFAULT_PORT
    
    try:
        publish_interval_ms = float(interval_input) if interval_input else DEFAULT_INTERVAL_MS
    except ValueError:
        print("无效的间隔时间，使用默认值")
        publish_interval_ms = DEFAULT_INTERVAL_MS
    
    # 创建并启动客户端
    client = MQTTClient()
    client.connect(broker_address, broker_port, publish_interval_ms)